package com.gitee.wsl.collections.map.bikey

import com.gitee.wsl.base.Objects.equals
import com.gitee.wsl.func.BiFunction
import com.gitee.wsl.func.TriFunction
import com.gitee.wsl.func.consumer.BiConsumer
import com.gitee.wsl.func.consumer.TriConsumer


interface BiKeyMap<R, C, V> : Iterable<BiKeyEntry<R, C, V>> {

    val size:Int

    /**
     * Associates the specified value with the specified bikey in this map. If
     * the map previously contained a mapping for the key, the old value is
     * replaced by the specified value.
     *
     * @param row
     * row with which the specified value is to be associated
     * @param column
     * column with which the specified value is to be associated
     * @param value
     * value to be associated with the specified bikey
     * @return the previous value associated with <tt>bikey</tt>, or
     * <tt>null</tt> if there was no mapping for <tt>bikey</tt>.
     * @throws NullPointerException
     * if the specified bikey or value is null
     */
    fun put(row: R, column: C, value: V): V

    /**
     * Copies all of the mappings from the specified map to this map. The effect
     * of this call is equivalent to that of calling
     * `put(row, column, value)` on this map once for each mapping from
     * bikey `row` and `column` to value `v` in the specified
     * map. The behavior of this operation is undefined if the specified map is
     * modified while the operation is in progress.
     *
     * @param m
     * mappings to be stored in this map
     * @throws NullPointerException
     * if the specified map is null, or if the specified map
     * contains null keys or values
     * @throws IllegalArgumentException
     * if some property of a key or value in the specified map
     * prevents it from being stored in this map
     */
    fun putAll(m: BiKeyMap<out R, out C, out V>) {
        m.forEach({ row: R, column: C, value: V -> this.put(row, column, value) })
    }

    /**
     * Returns the value to which the specified bikey is mapped, or `null`
     * if this map contains no mapping for the bikey.
     *
     * @param row
     * row whose associated value with column value is to be returned
     * @param column
     * column whose associated value with row value is to be returned
     * @return the value to which the specified bikey is mapped, or `null`
     * if this map contains no mapping for the key
     * @throws NullPointerException
     * if the specified row or column is null
     */
    fun get(row: R, column: C): V?

    /**
     * Removes the mapping for a bikey from this map if it is present.
     *
     *
     *
     * Returns the value to which this map previously associated the bikey, or
     * <tt>null</tt> if the map contained no mapping for the key.
     *
     *
     *
     * The map will not contain a mapping for the specified bikey once the call
     * returns.
     *
     * @param row
     * row whose mapping is to be removed from the map
     * @param column
     * column whose mapping is to be removed from the map
     * @return the previous value associated with <tt>bikey</tt>, or
     * <tt>null</tt> if there was no mapping for <tt>key</tt>.
     * @throws NullPointerException
     * if the specified bikey is null.
     */
    fun remove(row: R, column: C): V?

    /**
     * Returns the number of bikey-value mappings in this map.
     *
     * @return the number of bikey-value mappings in this map
     */
    //fun size(): Int

    /**
     * Removes all of the mappings from this map. The map will be empty after
     * this call returns.
     */
    fun clear()

    /**
     * Returns a [Set] view of the bikeys contained in this map. The set
     * is backed by the map, so changes to the map are reflected in the set, and
     * vice-versa. If the map is modified while an iteration over the set is in
     * progress, the results of the iteration are undefined.
     *
     * @return a set view of the keys contained in this map
     */
    fun keySet(): Set<BiKey<R, C>>

    /**
     * Returns a [BiKeySet] view of the bikeys contained in this map. The
     * set is backed by the map, so changes to the map are reflected in the set.
     * If the map is modified while an iteration over the set is in progress,
     * the results of the iteration are undefined.
     *
     * @return a set view of the keys contained in this map
     */
    //fun bikeySet(): BiKeySet<R, C>

    /**
     * Returns a set of row keys that have one or more values in the map.
     * Changes to the set will update the underlying table.
     *
     * @return set of row keys
     */
    fun rowKeySet(): Set<R>

    /**
     * Returns a set of column keys that have one or more values in the map.
     * Changes to the set will update the underlying table.
     *
     * @return set of column keys
     */
    fun columnKeySet(): Set<C>

    /**
     * Returns a [Collection] view of the values contained in this map.
     * The collection is backed by the map, so changes to the map are reflected
     * in the collection. If the map is modified while an iteration over the
     * collection is in progress, the results of the iteration are undefined.
     *
     * @return a collection view of the values contained in this map
     */
    fun values(): Collection<V>

    /**
     * Returns a [Set] view of the mappings contained in this map. The set
     * is backed by the map, so changes to the map are reflected in the set.
     *
     * @return a set view of the mappings contained in this map
     */
    fun entrySet(): Set<BiKeyEntry<R, C, V>>

    /**
     * Compares the specified object with this map for equality. Returns
     * <tt>true</tt> if the given object is also a map and the two maps
     * represent the same mappings. More formally, two maps <tt>m1</tt> and
     * <tt>m2</tt> represent the same mappings if
     * <tt>m1.entrySet().equals(m2.entrySet())</tt>. This ensures that the
     * <tt>equals</tt> method works properly across different implementations of
     * the <tt>BiKeyMap</tt> interface.
     *
     * @param o
     * object to be compared for equality with this map
     * @return <tt>true</tt> if the specified object is equal to this map
     */
    //override fun equals(o: Any?): Boolean

    /**
     * Returns the hash code value for this map. The hash code of a map is
     * defined to be the sum of the hash codes of each entry in the map's
     * <tt>entrySet()</tt> view. This ensures that <tt>m1.equals(m2)</tt>
     * implies that <tt>m1.hashCode()==m2.hashCode()</tt> for any two maps
     * <tt>m1</tt> and <tt>m2</tt>, as required by the general contract of
     * [Object.hashCode].
     *
     * @return the hash code value for this map
     * @see BiKeyEntry.hashCode
     * @see Object.equals
     * @see .equals
     */
    //override fun hashCode(): Int

    /**
     * Performs the given action for each bikey in this map until all entries
     * have been processed or the action throws an exception
     *
     * @param action
     * The action to be performed for each bikey
     * @throws NullPointerException
     * if the specified action is null
     */
    fun forEachBiKey(action: BiConsumer<in R, in C>)

    /**
     * Performs the given action for each entry in this map until all entries
     * have been processed or the action throws an exception.
     *
     * @param action
     * The action to be performed for each entry
     * @throws NullPointerException
     * if the specified action is null
     */
    fun forEach(action: BiConsumer<BiKey<R, C>, in V>) {
        forEach{ r, c, v -> action.accept(BiKey(r, c), v) }
    }

    /**
     * Performs the given action for each entry in this map until all entries
     * have been processed or the action throws an exception.
     *
     *
     *
     * The action processes bikey as a [TriConsumer] with a broken down
     * key.
     *
     * @param action
     * The action to be performed for each entry
     * @throws NullPointerException
     * if the specified action is null
     */
    fun forEach(action: TriConsumer<in R, in C, in V>)

//    override fun spliterator(): Spliterator<BiKeyEntry<R?, C?, V?>?> {
//        return Spliterators.spliterator(
//            iterator(), size(),
//            Spliterator.DISTINCT + Spliterator.NONNULL + Spliterator.SIZED
//        )
//    }

    /**
     * Returns <tt>true</tt> if this map maps one or more bikeys to the
     * specified value. More formally, returns <tt>true</tt> if and only if this
     * map contains at least one mapping to a value <tt>v</tt> such that
     * <tt>(value==null ? v==null : value.equals(v))</tt>. This operation
     * require time linear in the map size.
     *
     * @param value
     * value whose presence in this map is to be tested
     * @return <tt>true</tt> if this map maps one or more bikeys to the
     * specified value
     * @throws NullPointerException
     * if the specified value is null
     */
    fun containsValue(value: Any?): Boolean

    /**
     * Returns <tt>true</tt> if the map contains a mapping with the specified
     * row key.
     *
     * @param row
     * key of row to search for
     * @return <tt>true</tt> if the map contains a mapping with the specified
     * row.
     */
    fun containsRow(row: Any?): Boolean

    /**
     * Returns <tt>true</tt> if the map contains a mapping with the specified
     * column.
     *
     * @param column
     * key of column to search for
     * @return <tt>true</tt> if the map contains a mapping with the specified
     * column.
     */
    fun containsColumn(column: Any?): Boolean

    val isEmpty: Boolean
        /**
         * Returns <tt>true</tt> if this map contains no bikey-value mappings.
         *
         * @return <tt>true</tt> if this map contains no bikey-value mappings
         */
        get() = size == 0

    /**
     * Returns <tt>true</tt> if this map contains a mapping for the specified
     * bikey.
     *
     * @param row
     * row whose presence with column in this map are to be tested
     * @param column
     * column whose presence with row in this map are to be tested
     * @return <tt>true</tt> if this map contains a mapping for the specified
     * bikey
     * @throws NullPointerException
     * if the specified row or column is null
     */
    fun containsKey(row: R, column: C): Boolean {
        return get(row, column) != null
    }

    /**
     * Returns the value to which the specified bikey is mapped, or
     * `defaultValue` if this map contains no mapping for the bikey.
     *
     * @param row
     * row whose associated value is to be returned
     * @param column
     * column whose associated value is to be returned
     * @param defaultValue
     * the default mapping of the bikey
     * @return the value to which the specified bikey is mapped, or
     * `defaultValue` if this map contains no mapping for the
     * bikey
     */
    fun getOrDefault(row: R, column: C, defaultValue: V): V {
        val v = get(row, column)
        return if (v == null) defaultValue else v
    }

    /**
     * If the specified bikey is not already associated with a value associates
     * it with the given value and returns `null`, else returns the
     * current value.
     *
     * @param row
     * row with which the specified value is to be associated
     * @param column
     * column with which the specified value is to be associated
     * @param defaultValue
     * value to be associated with the specified key
     * @return the previous value associated with the specified bikey
     * @throws NullPointerException
     * if the specified keys or value is null
     */
    fun putIfAbsent(row: R, column: C, defaultValue: V): V {
        val v = get(row, column)
        return if (v == null) put(row, column, defaultValue) else v
    }

    /**
     * Removes the entry for the specified bikey only if it is currently mapped
     * to the specified value.
     *
     * @param row
     * row with which the specified value is to be associated
     * @param column
     * column with which the specified value is to be associated
     * @param value
     * value expected to be associated with the specified key
     * @return `true` if the value was removed
     * @throws NullPointerException
     * if the specified keys or value is null
     */
    fun remove(row: R, column: C, value: Any): Boolean {
        val currentValue = get(row, column)
        if (!equals(currentValue, value) || currentValue == null) {
            return false
        }
        remove(row, column)
        return true
    }

    /**
     * Replaces the entry for the specified bikey only if currently mapped to
     * the specified value.
     *
     * @param row
     * row with which the specified value is to be associated
     * @param column
     * column with which the specified value is to be associated
     * @param oldValue
     * value expected to be associated with the specified bikey
     * @param newValue
     * value to be associated with the specified bikey
     * @return `true` if the value was replaced
     * @throws NullPointerException
     * if a specified key or newValue is null
     * @throws NullPointerException
     * if oldValue is null
     */
    fun replace(row: R, column: C, oldValue: V, newValue: V): Boolean {
        val currentValue= get(row, column)
        if (!equals<Any?>(currentValue, oldValue) || currentValue == null) {
            return false
        }
        put(row, column, newValue)
        return true
    }

    /**
     * Replaces the entry for the specified bikey only if it is currently mapped
     * to some value.
     *
     * @param row
     * row with which the specified value is to be associated
     * @param column
     * column with which the specified value is to be associated
     * @param value
     * value to be associated with the specified key
     * @return the previous value associated with the specified key, or
     * `null` if there was no mapping for the key
     * @throws NullPointerException
     * if the specified key or value is null
     */
    fun replace(row: R, column: C, value: V): V? {
        val currentValue = get(row, column)
        return if (currentValue != null) put(row, column, value) else currentValue
    }

    /**
     * If the specified bikey is not already associated with a value, attempts
     * to compute its value using the given mapping function and enters it into
     * this map unless `null`.
     *
     *
     *
     * If the mapping function returns `null`, no mapping is recorded. If
     * the mapping function itself throws an (unchecked) exception, the
     * exception is rethrown, and no mapping is recorded. The mapping function
     * should not modify this map during computation.
     *
     * @param row
     * row with which the specified value is to be associated
     * @param column
     * column with which the specified value is to be associated
     * @param mappingFunction
     * the mapping function to compute a value
     * @return the current (existing or computed) value associated with the
     * specified bikey, or null if the computed value is null
     * @throws NullPointerException
     * if the specified bikey is null or the mappingFunction is null
     */
    fun computeIfAbsent(row: R, column: C, mappingFunction: BiFunction<R, C, out V?>): V? {
        val v = get(row, column)
        if (v == null) {
            val newValue: V? = mappingFunction.apply(row, column)
            if (newValue != null) {
                put(row, column, newValue)
                return newValue
            }
        }
        return v
    }

    /**
     * If the value for the specified bikey is present and non-null, attempts to
     * compute a new mapping given the bikey and its current mapped value.
     *
     *
     *
     * If the remapping function returns `null`, the mapping is removed.
     * If the remapping function itself throws an (unchecked) exception, the
     * exception is rethrown, and the current mapping is left unchanged.
     *
     *
     *
     * The remapping function should not modify this map during computation.
     *
     * @param row
     * row with which the specified value is to be associated
     * @param column
     * column with which the specified value is to be associated
     * @param remappingFunction
     * the remapping function to compute a value
     * @return the new value associated with the specified key, or null if none
     * @throws NullPointerException
     * if the specified bikey is null or the mappingFunction is null
     */
    fun computeIfPresent(
        row: R, column: C,
        remappingFunction: TriFunction<in R, in C, in V, out V?>,
    ): V? {
        val oldValue = get(row, column)
        if (oldValue != null) {
            val newValue: V? = remappingFunction.apply(row, column, oldValue)
            if (newValue != null) {
                put(row, column, newValue)
                return newValue
            }
            remove(row, column)
        }
        return null
    }

    /**
     * Attempts to compute a mapping for the specified bikey and its current
     * mapped value or `null` if there is no current mapping.
     *
     *
     *
     * If the function returns `null`, the mapping is removed (or remains
     * absent if initially absent). If the function itself throws an (unchecked)
     * exception, the exception is rethrown, and the current mapping is left
     * unchanged.
     *
     * @param row
     * row with which the specified value is to be associated
     * @param column
     * column with which the specified value is to be associated
     * @param remappingFunction
     * the function to compute a value
     * @return the new value associated with the specified bikey, or null if
     * none
     * @throws NullPointerException
     * if the specified key is null or the remappingFunction is null
     */
    fun compute(row: R, column: C, remappingFunction: TriFunction<in R, in C, in V?, out V?>): V? {

        val oldValue = get(row, column)
        val newValue: V? = remappingFunction.apply(row, column, oldValue)
        if (newValue != null) {
            put(row, column, newValue)
            return newValue
        }
        if (oldValue != null) {
            remove(row, column)
        }
        return null
    }

    /**
     * If the specified bikey is not already associated with a value associates
     * it with the given non-null value. Otherwise, replaces the associated
     * value with the results of the given remapping function, or removes if the
     * result is `null`. This method may be of use when combining multiple
     * mapped values for a bikey.
     *
     *
     *
     * If the function returns `null` the mapping is removed. If the
     * function itself throws an (unchecked) exception, the exception is
     * rethrown, and the current mapping is left unchanged.
     *
     * @param row
     * row with which the specified value is to be associated
     * @param column
     * column with which the specified value is to be associated
     * @param value
     * the non-null value to be merged with the existing value
     * associated with the bikey or, if no existing value, to be
     * associated with the key
     * @param remappingFunction
     * the function to recompute a value if present
     * @return the new value associated with the specified bikey, or null if no
     * value is associated with the key
     * @throws NullPointerException
     * if the specified key is null or the value or
     * remappingFunction is null
     */
    fun merge(row: R, column: C, value: V, remappingFunction: BiFunction<in V, in V, out V?>): V? {
        val oldValue = get(row, column)
        var newValue:V? = value
        if (oldValue != null) {
            newValue = remappingFunction.apply(oldValue, value)
        }
        if (newValue == null) {
            remove(row, column)
        } else {
            put(row, column, newValue)
        }
        return newValue
    }
}
